package itsm.isperp.framework.utils;

import itsm.isperp.framework.core.context.ContextHolder;
import itsm.isperp.framework.data.dialect.DialectFactory;
import itsm.isperp.framework.domain.BaseBean;
import itsm.isperp.framework.domain.Comparison;
import itsm.isperp.framework.domain.IBean;
import itsm.isperp.framework.domain.IUser;
import itsm.isperp.framework.web.request.JqGridFilterRule;
import itsm.isperp.framework.web.request.JqGridFilterRuleOp;
import itsm.isperp.module.service.db.DbEntityFieldService;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Table;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * 
 * bean工具类
 * 
 * @author lizx
 * @date 2013-10-5
 * @since 1.0
 * 
 */
public final class BeanUtils {

	static final Logger logger = LoggerFactory.getLogger(BeanUtils.class);

	/**
	 * 
	 * @param id
	 * @param tableName
	 * @param userName
	 * @param userFullname
	 */
	public static void doDatalog(final String id, final String tableName,
			final String userName, final String userFullname) {

		JdbcTemplate jdbc = ContextHolder.getSpringBean(JdbcTemplate.class);
		try {
			final Map<String, Object> obj = jdbc.queryForMap("SELECT * FROM "
					+ tableName + " WHERE ID='" + id + "'");

			jdbc.update(
					"INSERT INTO app_log_action(id,module_name,description,action_type,actioner,actioner_name,create_time) VALUES(?,?,?,?,?,?,?)",
					new PreparedStatementSetter() {
						public void setValues(PreparedStatement ps)
								throws SQLException {
							ps.setString(1, HibernateUUIDGenerator.generate());
							ps.setString(2, tableName);
							ps.setString(3, JsonBuilder.buildNonDefaultBinder()
									.toJson(obj));
							ps.setString(4, "删除");
							ps.setString(5, userName);
							ps.setString(6, userFullname);

							ps.setTimestamp(7, new java.sql.Timestamp(
									new Date().getTime()));

						}
					});
		} catch (Exception ex) {
			logger.error(ex.getMessage(), ex);
		}

	}

	/**
	 * 
	 * @param jdbc
	 * @param tableName
	 * @param id
	 * @param action
	 * @param userName
	 * @param userFullname
	 */
	protected static <T extends IBean> void insertActionLog(
			final JdbcTemplate jdbc, final T entity, final String tableName,
			final String id, final String action, final String userName,
			final String userFullname) {
		jdbc.update(
				"INSERT INTO app_log_action(id,module_name,description,action_type,actioner,actioner_name,create_time) VALUES(?,?,?,?,?,?,?)",
				new PreparedStatementSetter() {
					public void setValues(PreparedStatement ps)
							throws SQLException {
						ps.setString(1, HibernateUUIDGenerator.generate());
						ps.setString(2, tableName);
						ps.setString(3, JsonBuilder.buildNonDefaultBinder()
								.toJson(entity));
						ps.setString(4, action);
						ps.setString(5, userName);
						ps.setString(6, userFullname);

						ps.setTimestamp(7,
								new java.sql.Timestamp(new Date().getTime()));

					}
				});
	}

	public static <T extends IBean> String getDatalogDescription(
			final T entity, final Class<?> clazz)
			throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, IntrospectionException {

		Table table = clazz.getAnnotation(Table.class);

		DbEntityFieldService dbEntityFieldService = ContextHolder
				.getSpringBean("dbEntityFieldService");

		String[] columns = new String[] { "name", "column_name" };
		String[] columns2 = new String[] { "name", "column_name" };

		List<Map<String, Object>> list = dbEntityFieldService
				.findAllByEntityTablename(table.name(), columns2,
						new JqGridFilterRule("data_change_log",
								JqGridFilterRuleOp.eq, "1"));

		return getDatalogDescription(entity, list, columns[0], columns[1]);

	}

	public static <T extends IBean> void doDatalog(final T entity,
			final String tableName, final Set<String> includeColumns,
			final int type, final String userName, final String userFullname)
			throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, IntrospectionException {

		DbEntityFieldService dbEntityFieldService = ContextHolder
				.getSpringBean(DbEntityFieldService.class);

		JdbcTemplate jdbc = ContextHolder.getSpringBean(JdbcTemplate.class);

		insertActionLog(jdbc, entity, tableName, entity.getId(),
				(type == 0 ? "新增" : "更新"), userName, userFullname);

		String[] columns = new String[] { "name", "column_name" };
		String sql = "";
		// 第一得到需要记录日志的业务实体及其字段
		List<Map<String, Object>> list = dbEntityFieldService
				.findAllByEntityTablename(tableName, columns,
						new JqGridFilterRule("data_change_log",
								JqGridFilterRuleOp.eq, "1"));

		if (list != null && list.size() > 0) {

			Map<String, Object> oldEntity = null;

			if (type == 1) {

				if (includeColumns == null) {
					sql = "SELECT * FROM " + tableName;
				} else {
					sql = "SELECT " + list.get(0).get("column_name").toString();

					int lsize = list.size();
					for (int i = 1; i < lsize; i++) {
						sql += "," + list.get(i).get("column_name").toString();
					}

					sql += " FROM " + tableName;

				}

				sql += " WHERE id='" + entity.getId() + "' ";
				// Class<? extends IBean> clazz = entity.getClass();
				oldEntity = jdbc.queryForMap(DialectFactory.getDialect()
						.getLimitString(sql));
			}

			// 对比字段数据是否发生变化，记录描述信息
			final String description = compareObject(oldEntity, entity, list,
					includeColumns, "name", "column_name", type == 0 ? ""
							: "->");

			if (org.apache.commons.lang3.StringUtils.isNotEmpty(description)) {
				// DbEntityDatalog dbLog = new DbEntityDatalog();
				// dbLog.setBusinessId(entity.getId());
				// dbLog.setTableName(tableName);
				// dbLog.setDescription(result);
				// dbEntityDatalogService.save(dbLog);

				jdbc.update(
						"INSERT INTO db_entity_datalog(id,business_id,table_name,description,creator,creator_name,create_date,modifier,modifier_name,modify_date) VALUES(?,?,?,?,?,?,?,?,?,?)",
						new PreparedStatementSetter() {
							public void setValues(PreparedStatement ps)
									throws SQLException {
								ps.setString(1,
										HibernateUUIDGenerator.generate());
								ps.setString(2, entity.getId());
								ps.setString(3, tableName);
								ps.setString(4, description);

								ps.setString(5, userName);

								ps.setString(6, userFullname);

								ps.setTimestamp(7, new java.sql.Timestamp(
										new Date().getTime()));

								ps.setString(8, userName);

								ps.setString(9, userFullname);

								ps.setTimestamp(10, new java.sql.Timestamp(
										new Date().getTime()));

							}
						});

			}

		}
	}

	public static <T extends IBean> String getDatalogDescription(
			final T afterobj, final List<Map<String, Object>> cols,
			final String nameField, final String columnNameField)
			throws IntrospectionException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException {

		Assert.notNull(afterobj);

		// 去除对象所有字段
		PropertyDescriptor[] descriptors = Introspector.getBeanInfo(
				afterobj.getClass()).getPropertyDescriptors();

		String result = "";

		for (Map<String, Object> fieldObj : cols) {

			String col = fieldObj.get(columnNameField).toString();

			String fieldName = StringUtils.camelCaseWord(col);

			String colFieldName = fieldObj.get(nameField).toString();

			for (PropertyDescriptor descriptor : descriptors) {

				String proname = descriptor.getName();

				if (org.apache.commons.lang3.StringUtils.isNotEmpty(proname)
						&& proname.equalsIgnoreCase(fieldName)) {
					Method readmethod = descriptor.getReadMethod();
					Object v = readmethod.invoke(afterobj);

					if (v != null)
						result += "【" + colFieldName + "】:" + v.toString()
								+ "；";

				}

			}

		}

		return result;
	}

	/**
	 * 
	 * 比较两个对象差异数据
	 * 
	 * @param beforeobj
	 * @param afterobj
	 * @param cols
	 *            比较字段
	 * @return
	 * @throws IntrospectionException
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * 
	 * @throws Exception
	 */
	public static String compareObject(final Map<String, Object> beforeobj,
			final Object afterobj, final List<Map<String, Object>> cols,
			final Set<String> includeColumns, final String nameField,
			final String columnNameField, final String action)
			throws IntrospectionException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException {

		Assert.notNull(afterobj);

		// Assert.isAssignable(beforeobj.getClass(), afterobj.getClass());

		// 去除对象所有字段
		PropertyDescriptor[] descriptors = Introspector.getBeanInfo(
				afterobj.getClass()).getPropertyDescriptors();

		String result = "";

		for (Map<String, Object> fieldObj : cols) {

			String col = fieldObj.get(columnNameField).toString();

			String fieldName = StringUtils.camelCaseWord(col.toLowerCase());

			String colFieldName = fieldObj.get(nameField).toString();

			if (includeColumns != null && !includeColumns.contains(fieldName)) {
				continue;
			}

			for (PropertyDescriptor descriptor : descriptors) {

				String proname = descriptor.getName();

				if (org.apache.commons.lang3.StringUtils.isNotEmpty(proname)
						&& proname.equalsIgnoreCase(fieldName)) {
					Method readmethod = descriptor.getReadMethod();
					Object v = readmethod.invoke(afterobj);
					if (beforeobj == null) {
						if (v != null)
							result += "【" + colFieldName + "】：" + action
									+ v.toString() + "；";
					} else {

						Object bv = beforeobj.get(col);

						if (bv == null || "".equals(bv.toString())) {
							if (v == null || "".equals(v.toString())) {
								continue;
							}
						}

						Comparison comparison = new Comparison();

						comparison.setBefore(bv);
						comparison.setAfter(v);
						comparison.setFieldType(descriptor.getPropertyType());

						if (!comparison.isSame()) {
							result += "【" + colFieldName + "】："
									+ comparison.getBeforeStirng() + action
									+ comparison.getAfterString() + "；";

						}
					}

				}

			}

		}

		return result;

	}

	//
	/**
	 * 设置bean的创建人，修改人，修改时间
	 * 
	 * @param entity
	 */
	public static final void autoSetEntityInfo(IBean entity,
			Set<String> includeColumns, int type) {

		if (entity instanceof BaseBean) {
			BaseBean bb = (BaseBean) entity;
			String userName = null, userFullName = null;

			if (org.apache.commons.lang3.StringUtils.isEmpty(bb.getCreator())
					&& org.apache.commons.lang3.StringUtils.isEmpty(bb
							.getModifier())) {
				if (ContextHolder.isLogin()) {
					IUser user = ContextHolder.getLoginUser();
					userName = user.getUsername();
					userFullName = user.getFullname();
				} else {
					userName = "admin";
					userFullName = "admin";
				}

			} else {
				userName = bb.getCreator();
				userFullName = bb.getCreatorName();
			}
			bb.setModifier(userName);
			bb.setModifierName(userFullName);
			bb.setModifyDate(new Timestamp(new Date().getTime()));

			if (includeColumns != null) {

				includeColumns.add("modifier");
				includeColumns.add("modifierName");
				includeColumns.add("modifyDate");
			}

			if (type == 0) {

				if (org.apache.commons.lang3.StringUtils.isEmpty(bb
						.getCreator())) {
					bb.setCreator(userName);
					bb.setCreatorName(userFullName);
					bb.setCreateDate(new Timestamp(new Date().getTime()));
				}
				if (includeColumns != null) {

					includeColumns.add("creator");
					includeColumns.add("creatorName");
					includeColumns.add("createDate");
				}
			}

		}
	}

	public static final void copyProperties(final Map<String, Object> source,
			Object target) throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Set<String> keys = source.keySet();

		for (String sourceP : keys) {

			String targetPdName = StringUtils.camelCaseWord(sourceP);

			PropertyDescriptor targetPd = org.springframework.beans.BeanUtils
					.getPropertyDescriptor(target.getClass(), targetPdName);

			if (targetPd != null) {
				try {

					Method writeMethod = targetPd.getWriteMethod();
					if (!Modifier.isPublic(writeMethod.getDeclaringClass()
							.getModifiers())) {
						writeMethod.setAccessible(true);
					}
					Object value = source.get(sourceP);
					writeMethod.invoke(target, value);
				} catch (Throwable ex) {
					throw new FatalBeanException(
							"Could not copy properties from source to target",
							ex);
				}
			}

		}

	}

	public static Set<String> getNotEmptyProperties(Object source) {
		Set<String> set = new HashSet<>();
		PropertyDescriptor[] sourcePds = org.springframework.beans.BeanUtils
				.getPropertyDescriptors(source.getClass());

		for (PropertyDescriptor targetPd : sourcePds) {
			Method readMethod = targetPd.getReadMethod();
			if (readMethod != null) {
				try {
					if (!Modifier.isPublic(readMethod.getDeclaringClass()
							.getModifiers())) {
						readMethod.setAccessible(true);
					}
					Object value = readMethod.invoke(source);

					if (value != null && !"".equals(value.toString())) {
						set.add(targetPd.getName());
					}
				} catch (Throwable ex) {
					throw new FatalBeanException("Could not copy property '"
							+ targetPd.getName() + "' from source to target",
							ex);
				}
			}

		}
		return set;
	}

	/**
	 * 复制非空字段到指定的目标对象
	 * 
	 * @param source
	 * @param target
	 * @param editable
	 * @param ignoreProperties
	 * @throws BeansException
	 */
	public static void copyNotEmptyProperties(Object source, Object target,
			String... ignoreProperties) throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();

		PropertyDescriptor[] targetPds = org.springframework.beans.BeanUtils
				.getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null) ? Arrays
				.asList(ignoreProperties) : null;

		for (PropertyDescriptor targetPd : targetPds) {
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null
					&& (ignoreProperties == null || (!ignoreList
							.contains(targetPd.getName())))) {
				PropertyDescriptor sourcePd = org.springframework.beans.BeanUtils
						.getPropertyDescriptor(source.getClass(),
								targetPd.getName());
				if (sourcePd != null) {
					Method readMethod = sourcePd.getReadMethod();
					if (readMethod != null
							&& ClassUtils.isAssignable(
									writeMethod.getParameterTypes()[0],
									readMethod.getReturnType())) {
						try {
							if (!Modifier.isPublic(readMethod
									.getDeclaringClass().getModifiers())) {
								readMethod.setAccessible(true);
							}
							Object value = readMethod.invoke(source);
							if (!Modifier.isPublic(writeMethod
									.getDeclaringClass().getModifiers())) {
								writeMethod.setAccessible(true);
							}
							if (value != null && !"".equals(value.toString())) {
								writeMethod.invoke(target, value);

							}
						} catch (Throwable ex) {
							throw new FatalBeanException(
									"Could not copy property '"
											+ targetPd.getName()
											+ "' from source to target", ex);
						}
					}
				}
			}
		}
	}

}
